import CryptoJS from 'crypto-js'
import { uniJumpWay } from './const-value'
import { isTabbar } from './router-name'
import { t } from '@/locale'
import { CatchError } from './catch-error'
import type { Parameter, UniRouteType } from './type'

/**
 * 加密请求报文
 */
export const encryption = (src: string, keyWord: string) => {
  const key = CryptoJS.enc.Utf8.parse(keyWord)
  const encryted = CryptoJS.AES.encrypt(src, key, {
    iv: key,
    mode: CryptoJS.mode.CFB,
    padding: CryptoJS.pad.NoPadding
  })
  return encryted.toString()
}

/**
 * 是否空值
 */
export const isEmpty = (prop?: any): prop is undefined | null => prop === undefined || prop === null

/**
 * 将一个对象拼接成 url 对象的 query 参数形式
 * 
 * 当不想传递其他配置，那么可以直接传递需要拼接的对象，但此对象中不可包含 key 为 obj 的属性
 * 
 * @默认配置项 prefix — ?、piece — &、joinSymbol — =、filterEmpty — true
 */
export const joinObject = (config: Parameter.JoinObject) => {
  const dc: Parameter.JoinObject = {
    obj: {},
    prefix: '?',
    piece: '&',
    joinSymbol: '=',
    filterEmpty: true
  }
  const isObj = (param: any): param is Parameter.JoinObject['obj'] => !param?.obj
  const strs = []

  if (isObj(config)) {
    dc.obj = config
  } else {
    Object.assign(dc, config)
  }

  for (const [key, value] of Object.entries(dc.obj!)) {
    if (dc.filterEmpty && (isEmpty(value) || value === '')) continue
    strs.push(key + dc.joinSymbol + value)
  }

  return (strs.length ? dc.prefix : '') + strs.join(dc.piece)
}

/**
 * 页面路由跳转
 * @example
 *  1. 不传递任何东西，默认使用 uni.navigateBack
 *  2. 直接传递一个字符串，如果是 tabbar url，那么会自动使用 uni.swtichTab，否则 navigateTo
 *  3. 传递一个对象，但 params = 字符串，如果传递了 openType，那么根据第二条推，此时 openType = navigateBack 无效
 *  4. 传递一个对象，params 可以是 openType 对象的跳转方式配置项
 */
export const toPage = <T extends keyof UniRouteType = 'navigateBack'>(
  config?: Partial<Parameter.ToPage<T>> | string | UniRouteType['navigateBack']) => {

  const { params, query = {}, openType = 'navigateBack' } = (config ?? {}) as any
  const dc = {
    query,
    params: { url: '' },
    openType
  }

  if (typeof config === 'string') dc.params.url = config
  if (typeof params === 'string') dc.params.url = params
  if (params && typeof params === 'object') dc.params = params
  if (dc.params.url && (!dc.openType || dc.openType === 'navigateBack')) dc.openType = 'navigateTo'
  if (isTabbar(dc.params.url)) dc.openType = 'switchTab'

  dc.params.url += joinObject(dc.query)

  return Object(uniJumpWay)[dc.openType](dc.params)
}

/**
 * 返回一个 promise，此 promise 完成时间 = time
 */
export const awaitSetTimeout = (time = 0) => new Promise(resolve => setTimeout(resolve, time))

/**
 * 复制内容
 * @默认配置项 joinSymbol — =、piece — &、successText — 复制成功，failText — 复制失败
 * @example
 *  1. 当 content = object 时，会扁平化第一层属性处理
 *  2. 当 content = function 时，会处理对应类型
 */
export const copyData = (config: Parameter.CopyData) => {
  const dc: Parameter.CopyData = Object.assign({
    content: '',
    joinSymbol: '=',
    piece: '&',
    successText: t('utils.copyData.successText'),
    failText: t('utils.copyData.failText'),
  }, config)
  const getContent = (content: Parameter.CopyData['content']): string => {
    if (typeof content === 'function') return getContent(content())
    if (typeof content === 'object') return joinObject({
      filterEmpty: false,
      joinSymbol: dc.joinSymbol,
      piece: dc.piece,
      prefix: '',
      obj: content
    })
    return String(content)
  }
  return new Promise<boolean>((resolve, reject) => {
    uni.setClipboardData({
      data: getContent(dc.content),
      success: () => {
        const str = typeof dc.successText === 'string' ? dc.successText : dc.successText?.() ?? ''
        CatchError.prompt(str)
        resolve(true)
      },
      fail: () => {
        const str = typeof dc.failText === 'string' ? dc.failText : dc.failText?.() ?? ''
        CatchError.prompt(str)
        reject(false)
      }
    })
  })
}

/**
 * 给一个数字的整数位指定位置添加符号
 * @默认配置项 digit — 3、separatory — ,
 */
export const formatNumber = (
  config: Parameter.FormatNumber['num'] | Parameter.FormatNumber) => {
  const dc: RequiredDeep<Parameter.FormatNumber> = {
    num: 0,
    digit: 3,
    separatory: ','
  }
  if (typeof config === 'number') {
    dc.num = config
  } else {
    Object.assign(dc, config)
  }

  const parts = String(dc.num).split('.')

  parts[0] = parts[0].replace(
    new RegExp(`\\B(?=(\\d{${dc.digit}})+(?!\\d))`, 'g'),
    dc.separatory
  )

  return parts.join('.')
}

/**
 * 保留小数位
 * @默认配置项 digit — 2
 */
export const keepDecimalPlaces = 
  <T extends Parameter.KeepDecimalPlaces | Parameter.KeepDecimalPlaces['num']>(config: T)
  : T extends Pick<Parameter.KeepDecimalPlaces, 'fillSymbol'> ? string : number => {
  const dc: RequiredDeep<Parameter.KeepDecimalPlaces> = {
    num: 0,
    digit: 2,
    fillSymbol: ''
  }
  if (typeof config === 'number') {
    dc.num = config
  } else {
    Object.assign(dc, config)
  }
  dc.digit = Math.max(0, dc.digit)

  const [integer, decimal = ''] = String(dc.num).split('.')
  const str = [
    integer,
    dc.digit > 0 ? '.' : '',
    decimal.slice(0, dc.digit).padEnd(dc.digit, dc.fillSymbol)
  ].join('')
  return (dc.fillSymbol ? str : +str) as any
}

/**
 * 获取页面元素几何信息
 */
export const getElementInfo = (selector: string) => {
  type Nodes = RequiredDeep<UniApp.NodeInfo>[]
  return new Promise<Nodes>(resolve =>
    uni.createSelectorQuery()
      .selectAll(selector)
      .boundingClientRect(nodes => resolve(<Nodes>nodes))
      .exec()
  )
}

/**
 * 将一个 kebab-case 字符串转为 PascalCase 字符串
 * @example hello-world -> HelloWorld
 */
export const kebabToPascal = <S extends string>(str: S) => {
  const result = str.split('-')
    .map(item => item.slice(0, 1).toUpperCase() + item.slice(1))
    .join('') as KebabToPascal<S>
  return result
}

/**
 * 将一个 PascalCase 字符串转为 kebab-case 字符串，可以传入 camelCase 字符串
 * @example HelloWorld -> hello-world
 */
export const pascalToKebab = <S extends string>(str: S) => {
  // 第一个英文大写的话会导致多一个 - 符号，所以需要裁掉转换后的第一个字符
  const isCapital = /^[A-Z]/.test(str)
  const result = str.replace(/[A-Z]/g, s => '-' + s.toLowerCase())?.
    slice(+isCapital) as PascalToKebab<S>
  return result
}

/**
 * 将一个字符串的首字母转成大写或小写
 */
export const toStrFirstCase = <T extends string>(str: T):
  T extends Uncapitalize<T> ? Capitalize<T> : Uncapitalize<T> => {
  const first = str.slice(0, 1)
  const last = str.slice(1)
  if(/^[A-Z]$/.test(first)) return first.toLowerCase() + last as any
  return first.toUpperCase() + last as any
}

export const useNamespace = <S extends string>(name: S) => {
  const joinSymbol = '__'
  const newName = pascalToKebab(name)
  const e = <E extends string>(str: E) => newName + joinSymbol + str as `${typeof newName}${typeof joinSymbol}${E}`

  return {
    e,
    b: newName
  }
}